package com.hspedu.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hspedu.seckill.config.AccessLimit;
import com.hspedu.seckill.pojo.Order;
import com.hspedu.seckill.pojo.SeckillMessage;
import com.hspedu.seckill.pojo.SeckillOrder;
import com.hspedu.seckill.pojo.User;
import com.hspedu.seckill.rabbitmq.MQSenderMessage;
import com.hspedu.seckill.service.GoodsService;
import com.hspedu.seckill.service.OrderService;
import com.hspedu.seckill.service.SeckillOrderService;
import com.hspedu.seckill.vo.GoodsVo;
import com.hspedu.seckill.vo.RespBean;
import com.hspedu.seckill.vo.RespBeanEnum;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {

  
    @Resource
    private GoodsService goodsService;

    @Resource
    private SeckillOrderService seckillOrderService;

    @Resource
    private OrderService orderService;

    @Resource
    private RedisTemplate redisTemplate;

    //定义map-记录秒杀商品是否还有库存
    private HashMap<Long, Boolean> entryStockMap = new HashMap<>();

    //装配消息的生产者/发送者
    @Resource
    private MQSenderMessage mqSenderMessage;

    
    @Resource
    private RedisScript<Long> script;


    //处理用户抢购请求/秒杀
    @RequestMapping("/doSeckill")
    public String doSeckill(Model model, User user, Long goodsId) {


        if (user == null) {//用户没有登录
            return "login";
        }
        //将user放入到model, 下一个模板可以使用
        model.addAttribute("user", user);

        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        
        if (goodsVo.getStockCount() < 1) {
            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
            return "secKillFail";
        }


        //判断用户是否复购-直接到Redis中,获取对应的秒杀订单,如果有,则说明已经抢购了
        SeckillOrder o = (SeckillOrder) redisTemplate.opsForValue()
                .get("order:" + user.getId() + ":" + goodsVo.getId());
        if (null != o) { //说明该用户已经抢购了该商品
            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "secKillFail";
        }

        if (entryStockMap.get(goodsId)) {
            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
            return "secKillFail";
        }

        //1 获取锁，setnx
        //得到一个 uuid 值，作为锁的值
        String uuid = UUID.randomUUID().toString();


        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //2 获取锁成功
        if (lock) {

            Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
            if (decrement < 0) {//说明这个商品已经没有库存
                //说明当前秒杀的商品，已经没有库存
                entryStockMap.put(goodsId, true);
                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
                redisTemplate.execute(script, Arrays.asList("lock"), uuid);
                model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
                return "secKillFail";
            }

            //释放分布式锁
            redisTemplate.execute(script, Arrays.asList("lock"), uuid);

        } else {
            //3 获取锁失败,返回信息
            model.addAttribute("errmsg", RespBeanEnum.SEC_KILL_RETRY.getMessage());
            return "secKillFail";//错误页面
        }

        
        //创建SeckillMessage
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSenderMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));
        model.addAttribute("errmsg", "排队中...");
        return "secKillFail";

    }

    //获取秒杀路径
    @RequestMapping("/path")
    @ResponseBody
   
    @AccessLimit(second = 5, maxCount = 5, needLogin = true)
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (user == null || goodsId < 0 || !StringUtils.hasText(captcha)) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }


        //校验用户输入的验证码是否正确
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check) {//如果校验失败
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        String path = orderService.createPath(user, goodsId);
        return RespBean.success(path);
    }


    //生成验证码-happyCaptcha
    @RequestMapping("/captcha")
    public void happyCaptcha(HttpServletRequest request, HttpServletResponse response, User user, Long goodsId) {
        
        HappyCaptcha.require(request, response)
                .style(CaptchaStyle.ANIM)               
                .type(CaptchaType.NUMBER)               //设置验证码内容为数字
                .length(6)                              
                .width(220)                            
                .height(80)                             
                .font(Fonts.getInstance().zhFont())     
                .build().finish();                      //生成并输出验证码

        //把验证码的值，保存Redis，设置了验证码的失效时间100s
        //key: captcha:userId:goodsId
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId
                , (String) request.getSession().getAttribute("happy-captcha"), 100, TimeUnit.SECONDS);
    }

    
    @Override
    public void afterPropertiesSet() throws Exception {

        //查询所有的秒杀商品
        List<GoodsVo> list = goodsService.findGoodsVo();
        
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        
        //秒杀商品库存量对应key : seckillGoods:商品id
        list.forEach(goodsVo -> {


            redisTemplate.opsForValue()
                    .set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());

            //初始化map
            //如果goodsId : false 表示有库存
            //如果goodsId : true 表示没有库存
            entryStockMap.put(goodsVo.getId(), false);

        });

    }
}
